﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;

namespace LooseChart
{
    /// <summary>
    /// 等高线图
    /// </summary>
    public class ContourSeries : XYSeries
    {
        /// <summary>
        /// 等高线分级基数
        /// </summary>
        public int GradeBase
        {
            get { return (int)GetValue(GradeBaseProperty); }
            set { SetValue(GradeBaseProperty, value); }
        }
        public static readonly DependencyProperty GradeBaseProperty = DependencyProperty.Register("GradeBase", typeof(int), typeof(ContourSeries), new PropertyMetadata(5, (d, e) => { if (d is ISeries series) series.Redraw(); }));

        /// <summary>
        /// 分级方式
        /// </summary>
        public GradeType GradeType
        {
            get { return (GradeType)GetValue(GradeTypeProperty); }
            set { SetValue(GradeTypeProperty, value); }
        }
        public static readonly DependencyProperty GradeTypeProperty = DependencyProperty.Register("GradeType", typeof(GradeType), typeof(ContourSeries), new PropertyMetadata(GradeType.Log, (d, e) => { if (d is ISeries series) series.Redraw(); }));

        /// <summary>
        /// 等高线线条类型
        /// </summary>
        public ContourLineType ContourLineType
        {
            get { return (ContourLineType)GetValue(ContourLineTypeProperty); }
            set { SetValue(ContourLineTypeProperty, value); }
        }
        public static readonly DependencyProperty ContourLineTypeProperty = DependencyProperty.Register("ContourLineType", typeof(ContourLineType), typeof(ContourSeries), new PropertyMetadata(ContourLineType.QuadraticBezier, (d, e) => { if (d is ISeries series) series.Redraw(); }));

        /// <summary>
        /// 颜色分级
        /// </summary>
        public bool MultipeColor
        {
            get { return (bool)GetValue(MultipeColorProperty); }
            set { SetValue(MultipeColorProperty, value); }
        }
        public static readonly DependencyProperty MultipeColorProperty = DependencyProperty.Register("MultipeColor", typeof(bool), typeof(ContourSeries), new PropertyMetadata(true, (d, e) => { if (d is ISeries series) series.Reshow(); }));


        public ContourSeries() : base() { }

        /// <summary>
        /// 绘制内容
        /// </summary>
        /// <param name="color">基础颜色</param>
        /// <param name="list">矢量数据流</param>
        /// <param name="dc">画布</param>
        protected override void DrawDrawingContext(Color color, List<StreamGeometry> list, DrawingContext dc)
        {
            var pa = color.A / 255d;
            if (MultipeColor)
            {
                var count = list.Count;
                for (int i = 0; i < count; i++)
                {
                    var stream = list[i];
                    if (stream.IsEmpty()) continue;
                    var c = GetGradientColor(i, count, color);
                    var fillbrush = new SolidColorBrush(Color.FromArgb(Convert.ToByte(FillColorA * pa), c.R, c.G, c.B));
                    fillbrush.Freeze();
                    var strokebrush = new SolidColorBrush(Color.FromArgb(Convert.ToByte(StrokeColorA * pa), c.R, c.G, c.B));
                    strokebrush.Freeze();
                    Brush fill = ShowFill ? fillbrush : null;
                    Pen pen = ShowStroke ? new Pen(strokebrush, StrokeThickness) : null;
                    if (pen != null) pen.Freeze();
                    dc.DrawGeometry(fill, pen, stream);
                }
            }
            else
            {
                var fillbrush = new SolidColorBrush(Color.FromArgb(Convert.ToByte(FillColorA * pa), color.R, color.G, color.B));
                fillbrush.Freeze();
                var strokebrush = new SolidColorBrush(Color.FromArgb(Convert.ToByte(StrokeColorA * pa), color.R, color.G, color.B));
                strokebrush.Freeze();
                Brush fill = ShowFill ? fillbrush : null;
                Pen pen = ShowStroke ? new Pen(strokebrush, StrokeThickness) : null;
                if (pen != null) pen.Freeze();
                foreach (var stream in list)
                {
                    if (stream.IsEmpty()) continue;
                    dc.DrawGeometry(fill, pen, stream);
                }
            }
        }

        /// <summary>
        /// 获取数据源的矢量数据流
        /// </summary>
        /// <param name="chartDataSource">数据源</param>
        /// <returns></returns>
        protected override async Task UpdateStreamGeometry(CancellationToken token)
        {
            if (!this.IsLoaded || token.IsCancellationRequested || XAxis == null || YAxis == null) return;
            var points = Statistics;
            if (points == null || points.Count <= 0)
            {
                MainGeometry = null;
                return;
            }
            var pointLength = PointLength;
            var halfPointLength = pointLength / 2d;
            var xParam = this.XAxis.GetConvertParam();
            var yParam = this.YAxis.GetConvertParam();
            var gradeBase = this.GradeBase;
            var gradeType = this.GradeType;
            var contourLineType = this.ContourLineType;
            try
            {
                var temp = await Task.Factory.StartNew<List<StreamGeometry>>(() =>
                {
                    var maxcount = points.Max(p => p.Value);
                    var mincount = points.Min(p => p.Value);
                    var range = maxcount - mincount;
                    if (range <= 0) return null;
                    var streamCount = 0d;
                    switch (gradeType)
                    {
                        case GradeType.Line:
                            streamCount = range / gradeBase + 1;
                            break;
                        case GradeType.Log:
                            streamCount = Convert.ToInt32(Math.Log(range, gradeBase)) + 1;
                            break;
                        default:
                            break;
                    }
                    Dictionary<int, List<LinePoints>> streamLinePoints = new Dictionary<int, List<LinePoints>>();
                    Dictionary<int, int> streamLineBaseCount = new Dictionary<int, int>();
                    for (int i = 0; i < streamCount; i++)
                    {
                        streamLinePoints[i] = new List<LinePoints>();
                        if (i == 0) streamLineBaseCount[i] = 0;
                        else
                        {
                            switch (gradeType)
                            {
                                case GradeType.Line:
                                    streamLineBaseCount[i] = i * gradeBase;
                                    break;
                                case GradeType.Log:
                                    streamLineBaseCount[i] = Convert.ToInt32(Math.Pow(gradeBase, i));
                                    break;
                                default:
                                    break;
                            }
                        }
                    }

                    var widthcount = Convert.ToInt32(xParam.Length / pointLength);
                    var heightcount = Convert.ToInt32(yParam.Length / pointLength);
                    for (int xIndex = 0; xIndex < widthcount; xIndex++)
                    {
                        if (token.IsCancellationRequested) return null;
                        for (int yIndex = 0; yIndex < heightcount; yIndex++)
                        {
                            if (token.IsCancellationRequested) return null;
                            var xLocation = xIndex * pointLength; ;
                            var yLocation = yIndex * pointLength;
                            var c1IntLocation = new Point(xLocation, yLocation);
                            var c1 = points.ContainsKey(c1IntLocation) ? points[c1IntLocation] : 0;
                            var c2IntLocation = new Point(xLocation + pointLength, yLocation);
                            var c2 = points.ContainsKey(c2IntLocation) ? points[c2IntLocation] : 0;
                            var c3IntLocation = new Point(xLocation + pointLength, yLocation + pointLength);
                            var c3 = points.ContainsKey(c3IntLocation) ? points[c3IntLocation] : 0;
                            var c4IntLocation = new Point(xLocation, yLocation + pointLength);
                            var c4 = points.ContainsKey(c4IntLocation) ? points[c4IntLocation] : 0;

                            for (int i = 0; i < streamCount; i++)
                            {
                                if (token.IsCancellationRequested) return null;
                                var basecount = streamLineBaseCount[i];
                                var ps = streamLinePoints[i];
                                if (i == 0)
                                {
                                    if (streamLineBaseCount.ContainsKey(1) && (c1 >= streamLineBaseCount[1] || c2 >= streamLineBaseCount[1] || c3 >= streamLineBaseCount[1] || c4 >= streamLineBaseCount[1])) continue;
                                    if (c1 > 0) ps.Add(new LinePoints() { Begin = new Point(xLocation + halfPointLength - 1, yLocation + halfPointLength - 1) });
                                    break;
                                }
                                var num = ((c1 >= basecount ? 1 : 0) << 3) + ((c2 >= basecount ? 1 : 0) << 2) + ((c3 >= basecount ? 1 : 0) << 1) + (c4 >= basecount ? 1 : 0);
                                LinePointsAdd(num, ps, xIndex, yIndex, widthcount, heightcount, xLocation, yLocation, pointLength, halfPointLength);
                            }
                        }
                    }
                    List<StreamGeometry> streams = new List<StreamGeometry>();
                    for (int i = 0; i < streamCount; i++)
                    {
                        if (token.IsCancellationRequested) return null;
                        var stream = new StreamGeometry();
                        streams.Add(stream);
                        var ps = streamLinePoints[i];
                        using (var sgc = stream.Open())
                        {
                            if (i == 0)
                            {
                                //foreach (var item in ps)
                                //{
                                //    var otherpoints = new Point[]
                                //    {
                                //        new Point(item.Begin.X + 2, item.Begin.Y),
                                //        new Point(item.Begin.X + 2, item.Begin.Y + 2),
                                //        new Point(item.Begin.X, item.Begin.Y + 2)
                                //    };
                                //    sgc.BeginFigure(item.Begin, true, true);
                                //    sgc.PolyLineTo(otherpoints, true, false);
                                //}
                            }
                            else
                            {
                                while (ps.Count > 0)
                                {
                                    List<Point> temps = new List<Point>();
                                    var begin = ps[0].Begin;
                                    var end = ps[0].End;
                                    temps.Add(begin);
                                    temps.Add(end);
                                    ps.RemoveAt(0);
                                    var last = ps.FirstOrDefault(p => p.End == begin);
                                    while (last != null)
                                    {
                                        ps.Remove(last);
                                        begin = last.Begin;
                                        temps.Insert(0, begin);
                                        last = ps.FirstOrDefault(p => p.End == begin);
                                    }
                                    var next = ps.FirstOrDefault(p => p.Begin == end);
                                    while (next != null)
                                    {
                                        ps.Remove(next);
                                        end = next.End;
                                        temps.Add(end);
                                        next = ps.FirstOrDefault(p => p.Begin == end);
                                    }
                                    sgc.BeginFigure(temps[0], true, true);
                                    switch (contourLineType)
                                    {
                                        case ContourLineType.Bezier:
                                            if (temps.Count % 3 == 1) temps.RemoveAt(0);
                                            else if (temps.Count % 3 == 2) temps.Add(temps[0]);
                                            sgc.PolyBezierTo(temps, true, false);//三次贝塞尔曲线
                                            break;
                                        case ContourLineType.QuadraticBezier:
                                            if (temps.Count % 2 != 0) temps.RemoveAt(0);
                                            sgc.PolyQuadraticBezierTo(temps, true, false);//二次贝塞尔曲线
                                            break;
                                        case ContourLineType.Line:
                                        default:
                                            temps.RemoveAt(0);
                                            sgc.PolyLineTo(temps, true, false);//线性
                                            break;
                                    }
                                }
                            }
                        }
                        stream.Freeze();
                    }
                    return streams;
                }, token);
                if (!token.IsCancellationRequested) MainGeometry = temp;
            }
            catch (TaskCanceledException e)
            {
                Debug.WriteLine(string.Concat("等高线图生成矢量图线程被取消", e));
            }
        }


        private void LinePointsAdd(int num, List<LinePoints> ps, int xIndex, int yIndex, int widthcount, int heightcount, double xLocation, double yLocation, int pointLength, double halfPointLength)
        {
            switch (num)
            {
                case 0:
                    break;
                case 1:
                    ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation + halfPointLength), End = new Point(xLocation + halfPointLength, yLocation + pointLength) });
                    if (xIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation + pointLength), End = new Point(xLocation, yLocation + halfPointLength) });
                    if (yIndex == heightcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + halfPointLength, yLocation + pointLength), End = new Point(xLocation, yLocation + pointLength) });
                    break;
                case 2:
                    ps.Add(new LinePoints() { Begin = new Point(xLocation + halfPointLength, yLocation + pointLength), End = new Point(xLocation + pointLength, yLocation + halfPointLength) });
                    if (xIndex == widthcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation + halfPointLength), End = new Point(xLocation + pointLength, yLocation + pointLength) });
                    if (yIndex == heightcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation + pointLength), End = new Point(xLocation + halfPointLength, yLocation + pointLength) });
                    break;
                case 3:
                    ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation + halfPointLength), End = new Point(xLocation + pointLength, yLocation + halfPointLength) });
                    if (xIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation + pointLength), End = new Point(xLocation, yLocation + halfPointLength) });
                    if (xIndex == widthcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation + halfPointLength), End = new Point(xLocation + pointLength, yLocation + pointLength) });
                    if (yIndex == heightcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation + pointLength), End = new Point(xLocation, yLocation + pointLength) });
                    break;
                case 4:
                    ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation + halfPointLength), End = new Point(xLocation + halfPointLength, yLocation) });
                    if (yIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation + halfPointLength, yLocation), End = new Point(xLocation + pointLength, yLocation) });
                    if (xIndex == widthcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation), End = new Point(xLocation + pointLength, yLocation + halfPointLength) });
                    break;
                case 5:
                    ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation + halfPointLength), End = new Point(xLocation + halfPointLength, yLocation) });
                    ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation + halfPointLength), End = new Point(xLocation + halfPointLength, yLocation + pointLength) });
                    if (xIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation + pointLength), End = new Point(xLocation, yLocation + halfPointLength) });
                    if (yIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation + halfPointLength, yLocation), End = new Point(xLocation + pointLength, yLocation) });
                    if (xIndex == widthcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation), End = new Point(xLocation + pointLength, yLocation + halfPointLength) });
                    if (yIndex == heightcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + halfPointLength, yLocation + pointLength), End = new Point(xLocation, yLocation + pointLength) });
                    break;
                case 6:
                    ps.Add(new LinePoints() { Begin = new Point(xLocation + halfPointLength, yLocation + pointLength), End = new Point(xLocation + halfPointLength, yLocation) });
                    if (yIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation + halfPointLength, yLocation), End = new Point(xLocation + pointLength, yLocation) });
                    if (xIndex == widthcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation), End = new Point(xLocation + pointLength, yLocation + pointLength) });
                    if (yIndex == heightcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation + pointLength), End = new Point(xLocation + halfPointLength, yLocation + pointLength) });
                    break;
                case 7:
                    ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation + halfPointLength), End = new Point(xLocation + halfPointLength, yLocation) });
                    if (xIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation + pointLength), End = new Point(xLocation, yLocation + halfPointLength) });
                    if (yIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation + halfPointLength, yLocation), End = new Point(xLocation + pointLength, yLocation) });
                    if (xIndex == widthcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation), End = new Point(xLocation + pointLength, yLocation + pointLength) });
                    if (yIndex == heightcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation + pointLength), End = new Point(xLocation, yLocation + pointLength) });
                    break;
                case 8:
                    ps.Add(new LinePoints() { Begin = new Point(xLocation + halfPointLength, yLocation), End = new Point(xLocation, yLocation + halfPointLength) });
                    if (xIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation + halfPointLength), End = new Point(xLocation, yLocation) });
                    if (yIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation), End = new Point(xLocation + halfPointLength, yLocation) });
                    break;
                case 9:
                    ps.Add(new LinePoints() { Begin = new Point(xLocation + halfPointLength, yLocation), End = new Point(xLocation + halfPointLength, yLocation + pointLength) });
                    if (xIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation + pointLength), End = new Point(xLocation, yLocation) });
                    if (yIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation), End = new Point(xLocation + halfPointLength, yLocation) });
                    if (yIndex == heightcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + halfPointLength, yLocation + pointLength), End = new Point(xLocation, yLocation + pointLength) });
                    break;
                case 10:
                    ps.Add(new LinePoints() { Begin = new Point(xLocation + halfPointLength, yLocation), End = new Point(xLocation + pointLength, yLocation + halfPointLength) });
                    ps.Add(new LinePoints() { Begin = new Point(xLocation + halfPointLength, yLocation + pointLength), End = new Point(xLocation, yLocation + halfPointLength) });
                    if (xIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation + halfPointLength), End = new Point(xLocation, yLocation) });
                    if (yIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation), End = new Point(xLocation + halfPointLength, yLocation) });
                    if (xIndex == widthcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation + halfPointLength), End = new Point(xLocation + pointLength, yLocation + pointLength) });
                    if (yIndex == heightcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation + pointLength), End = new Point(xLocation + halfPointLength, yLocation + pointLength) });
                    break;
                case 11:
                    ps.Add(new LinePoints() { Begin = new Point(xLocation + halfPointLength, yLocation), End = new Point(xLocation + pointLength, yLocation + halfPointLength) });
                    if (xIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation + pointLength), End = new Point(xLocation, yLocation) });
                    if (yIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation), End = new Point(xLocation + halfPointLength, yLocation) });
                    if (xIndex == widthcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation + halfPointLength), End = new Point(xLocation + pointLength, yLocation + pointLength) });
                    if (yIndex == heightcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation + pointLength), End = new Point(xLocation, yLocation + pointLength) });
                    break;
                case 12:
                    ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation + halfPointLength), End = new Point(xLocation, yLocation + halfPointLength) });
                    if (xIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation + halfPointLength), End = new Point(xLocation, yLocation) });
                    if (yIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation), End = new Point(xLocation + pointLength, yLocation) });
                    if (xIndex == widthcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation), End = new Point(xLocation + pointLength, yLocation + halfPointLength) });
                    break;
                case 13:
                    ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation + halfPointLength), End = new Point(xLocation + halfPointLength, yLocation + pointLength) });
                    if (xIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation + pointLength), End = new Point(xLocation, yLocation) });
                    if (yIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation), End = new Point(xLocation + pointLength, yLocation) });
                    if (xIndex == widthcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation), End = new Point(xLocation + pointLength, yLocation + halfPointLength) });
                    if (yIndex == heightcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + halfPointLength, yLocation + pointLength), End = new Point(xLocation, yLocation + pointLength) });
                    break;
                case 14:
                    ps.Add(new LinePoints() { Begin = new Point(xLocation + halfPointLength, yLocation + pointLength), End = new Point(xLocation, yLocation + halfPointLength) });
                    if (xIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation + halfPointLength), End = new Point(xLocation, yLocation) });
                    if (yIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation), End = new Point(xLocation + pointLength, yLocation) });
                    if (xIndex == widthcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation), End = new Point(xLocation + pointLength, yLocation + pointLength) });
                    if (yIndex == heightcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation + pointLength), End = new Point(xLocation + pointLength, yLocation + pointLength) });
                    break;
                case 15:
                    if (xIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation + pointLength), End = new Point(xLocation, yLocation) });
                    if (yIndex == 0) ps.Add(new LinePoints() { Begin = new Point(xLocation, yLocation), End = new Point(xLocation + pointLength, yLocation) });
                    if (xIndex == widthcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation), End = new Point(xLocation + pointLength, yLocation + pointLength) });
                    if (yIndex == heightcount - 2) ps.Add(new LinePoints() { Begin = new Point(xLocation + pointLength, yLocation + pointLength), End = new Point(xLocation, yLocation + pointLength) });
                    break;
                default:
                    break;
            }
        }
    }

    /// <summary>
    /// 分级方式
    /// </summary>
    public enum GradeType
    {
        /// <summary>
        /// 线性
        /// </summary>
        Line = 0,
        /// <summary>
        /// 对数
        /// </summary>
        Log = 1,
    }

    internal class LinePoints
    {
        public Point Begin { get; set; }
        public Point End { get; set; }

    }

    public enum ContourLineType
    {
        /// <summary>
        /// 直线
        /// </summary>
        Line = 0,
        /// <summary>
        /// 三次贝塞尔曲线
        /// </summary>
        Bezier = 1,
        /// <summary>
        /// 二次贝塞尔曲线
        /// </summary>
        QuadraticBezier = 2,
    }
}
